from em_485 import Em485
import _thread
import machine
from machine import Pin,Timer,ADC # 从machine模块导入Pin，Timer子模块
from machine import RTC
from ble import BLEOP
import bluetooth
import time
from time import sleep_ms

#from OLED import oled
from binascii import hexlify
import json
import _thread
from GPIO import *
from switch import *
from display import *
from beep import *
from kalman import *
from loginfo import *
#蓝牙状态
blustatus=False
#485 任务
Read485Taskstatus=False
# 显示读表内容
display485 =False
#有新显示页
displayNew = False
#自动关机时间
powerofftime_ns=0
#自动回复首页
firstpagetime_ns =0
#读表后电表状态
metererror =False

#调用BLE 设置ESP32被发现的蓝牙名称 名称为主程序传参传入    

rtc = machine.RTC()
def main(blename="QCMeter"):

    def read485(DI):       
        #AAAAAAAAAAAA
        #999999999999
        i=0
        rs=None
        while True:
            rs = em485.read(com='2', Address='AAAAAAAAAAAA', data=DI, baudrate=1200)
            if rs!=None:
                break
            i=i+1
            if i>3:
                break
        return rs
         
    def readmetertask(lock):  # 创建按钮执行抄表任务
        
        global metererror
        
        metererror =False
        itemstr=['0201FF00','0202FF00','0001FF00','0002FF00','03300D00','03300D01','03300D02','0207FF00','0203FF00','0204FF00','0001FF01','0002FF01','040005FF',
                 '0400010C','02800001']
       
        itemids={'0201FF00':'电压','0202FF00':'电流',
                 '0001FF00':'当月正向有功电量','0002FF00':'当月反向有功电能量',
                 '03300D00':'开盖次数','03300D01':'上1次开盖记录','03300D02':'上2次开盖记录','0207FF00':'相角','0203FF00':'有功功率','0204FF00':'无功功率',
                 '0001FF01':'上月正向有功电能','0002FF01':'上月反向有功电能','040005FF':'电表状态字','0400010C':'电表时钟','02800001':'零线电流'}
        
       # 04 00 01 0C YYMMDDWWhhmmss
       # 02 80 00 01 XXX.XXX  A  零线电流 const val mitZeroLC = "ZeroLC"

        # 单相表=1 三相表=0
        items={}
        itemsDI={}
        data_packet_count=0
        metertype=0
        ledalarm.value(1)
        
        for key in itemstr:

            play_ding(0.5)
            lednormal.value(not lednormal.value())

            value = itemids[key]
         
            log.append(f'正在读取{value}')
            pagecontrol.clear()
            pagecontrol.addlinecn(nowread)
            pagecontrol.addlinecn(f'{value}')
            pagecontrol.nextshow()
            #nextpage()s
            rs =read485(key)
            
            if rs==None:
                break
            if rs["Address"] =='EEFFFFFFFFFFFFEE':
                if key=='03300D00':
                    itemstr.remove('03300D01')
                    itemstr.remove('03300D02')
                continue
            
            if key =='0201FF00':
                if(rs['UB']==None and rs['UC']==None):
                    metertype=1
                    itemstr.remove('0207FF00')
              
                rs.update({"Metertype":metertype})
  
                
            if key=='03300D00':
                if rs.get('Opencovertimes')==None:
                    rs.update({'Opencovertimes':0})
                if rs['Opencovertimes']<1:
                    itemstr.remove('03300D01')
                if rs['Opencovertimes']<2:
                    itemstr.remove('03300D02')
                    
            if rs!=None:
                data_packet_count=data_packet_count+1               
                items.update({data_packet_count:rs})
                itemsDI.update({key:rs})
          
              
        for data_packet_index  in range(1,data_packet_count+1):
            itemone = items.get(data_packet_index)
            itemone.update({"Index":data_packet_index})
            itemone.update({"Count":data_packet_count})
          
            json_str = json.dumps(itemone)
            
            if(json_str!=None and bleop.is_connected()==True):
                bleop.notify(json_str)
                sleep_ms(100)
      
         
        Uckd=[176.0,'<','0201FF00']
        Ickd=[0.0,'<=','0202FF00']
        packd=[30,'>=','0207FF00']
        
        pagecontrol.clear()
        checkdatas:dict[str, list]={}
        if(metertype==0):
            checkdatas ={'UA':Uckd,
                     'UB':Uckd,
                     'UC':Uckd,
                     'IA':Ickd,
                     'IC':Ickd,
                     'IB':Ickd,
                     'PAA':packd,
                     'PAC':packd,
                     'PAB':packd
                     }
            pagecontrol.addlinecn(threephasemeter)
        else:
            pagecontrol.addlinecn(onephasemeter)
            checkdatas ={'UA':Uckd,'IA':Ickd}
        
        pagecontrol.addline(str(itemsDI['0201FF00']['Address']))
        
        
        for ckd in checkdatas:
            di=checkdatas[ckd][2]                  
            if((itemsDI.get(di)!=None) and (itemsDI.get(di).get(ckd)!=None)):
                val = itemsDI.get(di).get(ckd)
                if(checkdata(val,checkdatas[ckd])):
                    metererror=True
                    log.append(ckd,val,"metererror",metererror)
        if(getitem(itemsDI,'040005FF','statusword')!=None):       
            if(len(itemsDI['040005FF']['statusword'])>0):
                metererror=True
        if(metererror):
            pagecontrol.addlinecn(abnormalstatus)
            pagecontrol.addlinecn(details)
        else:
            pagecontrol.addlinecn(normalstatus)
            play_dong()
        pagecontrol.newpage()

      
        if(metertype==0):
            showcn(itemids['0201FF00'])
            show(itemsDI,'0201FF00','UA')
            show(itemsDI,'0201FF00','UB')
            show(itemsDI,'0201FF00','UC')
            pagecontrol.newpage()
            showcn(itemids['0202FF00'])
            show(itemsDI,'0202FF00','IA')
            show(itemsDI,'0202FF00','IB')
            show(itemsDI,'0202FF00','IC')
            pagecontrol.newpage()
            showcn(itemids['0203FF00'])
            show(itemsDI,'0203FF00','PA')
            show(itemsDI,'0203FF00','PB')
            show(itemsDI,'0203FF00','PC')
            pagecontrol.newpage()
            showcn(itemids['0204FF00'])
            show(itemsDI,'0204FF00','QA')
            show(itemsDI,'0204FF00','QB')
            show(itemsDI,'0204FF00','QC')
            pagecontrol.newpage()
            showcn(itemids['0207FF00'])
            show(itemsDI,'0207FF00','PAA')
            show(itemsDI,'0207FF00','PAB')
            show(itemsDI,'0207FF00','PAC')
            pagecontrol.newpage()
            
        else:
            showcn(itemids['0201FF00'])
            show(itemsDI,'0201FF00','UA')     
            
            showcn(itemids['0202FF00'])
            show(itemsDI,'0202FF00','IA')
            
            pagecontrol.newpage()
            showcn(itemids['0203FF00'])
            show(itemsDI,'0203FF00','PA')
           
            showcn(itemids['0204FF00'])
            show(itemsDI,'0204FF00','QA')
            pagecontrol.newpage()
        
        PATotal= getitem(itemsDI,'0001FF00','PATotal')
        LPATotal= getitem(itemsDI,'0001FF01','LPATotal')
        RATotal= getitem(itemsDI,'0002FF00','RATotal')
        LPATotal= getitem(itemsDI,'0002FF01','LRATotal')
        #计算当前月电量
        if(PATotal!=None)and(LPATotal!=None)and(RATotal!=None)and(LPATotal!=None):
            
            try:    
                showcn(currentPA)
                    
                cPATotal = PATotal -LPATotal
                pagecontrol.addline(str(cPATotal))
            except  Exception as e:
                log.append(e)

            try:    
                showcn(currentRA)
                    
                cRATotal = RATotal -LPATotal
                pagecontrol.addline(str(cRATotal))
            except  Exception as e:
                log.append(e)       
            pagecontrol.newpage()
        
        showcn(itemids['0001FF00'])
        show(itemsDI,'0001FF00','PATotal')
        showcn(itemids['0002FF00'])
        show(itemsDI,'0002FF00','RATotal')
        pagecontrol.newpage()
        showcn(itemids['0001FF01'])
        show(itemsDI,'0001FF01','LPATotal')
        showcn(itemids['0002FF01'])
        show(itemsDI,'0002FF01','LRATotal')
        pagecontrol.newpage()
        showcn(itemids['03300D00'])
        show(itemsDI,'03300D00','Opencovertimes')
        nextpage()
        pagecontrol.newpage()
        showcn(itemids['0400010C'])
        show(itemsDI,'0400010C','MeterTime')
        showcn(itemids['02800001'])
        show(itemsDI,'02800001','ZeroLC')
        nextpage()

    def checkdata(val,check)->bool:
        checkresult = False
        
        checkmode =check[1]
        checkval = check[0]
        if (val != None):
            if checkmode=='=>':
                checkresult = val >= checkval
            elif checkmode=='<=':
               
                checkresult = val <= checkval               
            elif checkmode=='>':
                checkresult = val > checkval
            elif checkmode=='<':
                checkresult = val < checkval        
        return checkresult
    
    def showcn(text:str):
        pagecontrol.addlinecn(text)


    def show(itemsDI:dict,di:str,itemname:str):
        val =None
        try:
            val = getitem(itemsDI,di,itemname)
        except  Exception as e:
            val=None
            
        if val !=None:
            pagecontrol.addline(str(val))
        else:
            pagecontrol.addline(str(None))
            
    def getitem(itemsDI,di:str,itemname:str):
        if((itemsDI.get(di)!=None) and (itemsDI.get(di).get(itemname)!=None)):
            val = itemsDI.get(di).get(itemname)
            return val
        else:
            return None
        
    def doled():
        led.value(not led.value())
        
    #蓝牙连接状态改变事件
    def connected_event(states):
        global blustatus
        blustatus = states
       
        if not states:
            # 初始化定时器，设定周期100ms，模式为周期性的，回调函数
            #BleStateTimer.init(period=100, mode=Timer.PERIODIC, callback=lambda t: led.value(not led.value()))
            BleStateTimer.init(period=100, mode=Timer.PERIODIC, callback=lambda t:doled())
            log.append('蓝牙断开进入等待连接')  
        else:        
            led.value(1)   # 值为1表示蓝色LED亮   
            BleStateTimer.deinit()  # 取消计时器
            log.append('蓝牙连接进入通讯状态')
        firstpage()      
            
    #蓝牙接收到数据
    def on_rx(value):
        global firstpagetime_ns 
        global powerofftime_ns
        BLE_MSG = value
        #log.append("接收蓝牙发来的数据:",hexlify(BLE_MSG).decode('utf-8'))           
        #log.append("数据长度：",len(BLE_MSG))        
        log.append(BLE_MSG)
        rxdic = json.loads(BLE_MSG)
        #手机发送对时命令
        if(rxdic['Updatetime']!=None):
            datetimestr = rxdic['Updatetime']
            datetimes =  datetimestr.split() 
            d_t= datetimes[0].split('-')      
            year =int(''.join(d_t[0]))
            month = int(''.join(d_t[1]))
            day = int(''.join(d_t[2]))
            d_t= datetimes[1].split(':')      
            hour = int(''.join(d_t[0]))
            minute = int(''.join(d_t[1]))
            second = int(''.join(d_t[2]))
            print(year, month, day, hour, minute, second)
            rtc = machine.RTC()
            week=0
            rtc.datetime((year, month, day,week,hour, minute, second,0))
            firstpagetime_ns = time.time_ns()
            powerofftime_ns= time.time_ns()
            print(rtc.datetime())
            firstpage()
            
            
        # 清空接收的数据 
        BLE_MSG = None
         
    def nextpage():
        global displayNew
        displayNew =True
  
    def firstpage(info3 =""):
        #通过设置welcomepage的值为空刷新首页显示
        global display485
        global Read485Taskstatus
        if (display485 ==True):
            return
        if blustatus:
            welcomepage=welcomepageconnected
        else:
            welcomepage=welcomepagedisconnected

        t = time.localtime()
        if(info3==""):
            info3 ='{}-{}-{} {}:{}:{}'.format(t[0], t[1], t[2], t[3], t[4], t[5])

        welcomepage[3].info =info3
        pagecontrol.clear()                                
        pagecontrol.append(welcomepage)
        nextpage()
            
    def display485end():
        global display485
        display485 =False
        print("display485 =False")
        firstpage()

    def poweroff():
        log.append("关机指令运行 poweroff()")
        power.value(1)
        pagecontrol.clear()
        pagecontrol.append([lineinfo(upkeypoweroff,pixel(12,20+16+2),texttype.cn)])
        pagecontrol.nextshow()
        time.sleep(0.5)
        machine.deepsleep()
        while True:
            pass
        
    def deepsleep():
        # 设置设备进入深度睡眠
        log.append("sleep")
        pagecontrol.clear()
        pagecontrol.append([lineinfo(upkeypoweroff,pixel(12,20+16+2),texttype.cn)])
        pagecontrol.nextshow()
        wake_pin = Pin(pkey, Pin.IN, Pin.PULL_UP) # GPIO5 支持 RTC GPIO
        wake_pin.irq(trigger=Pin.WAKE_LOW, wake=machine.DEEPSLEEP) # 配置唤醒中断
        machine.deepsleep()
        
    #按键扫描处理
    def switchpress (key):
        
        global powerofftime_ns
        global firstpagetime_ns
        global metererror
        if(Read485Taskstatus):
            return
        if key == keyvalue.no_press:#5分钟没有键按下关机
            if((time.time_ns()-firstpagetime_ns)/1e9>=60*1):
                log.append('60秒没有按下')
                metererror=False
                display485end()
                firstpagetime_ns = time.time_ns()
                
            elif((time.time_ns()-powerofftime_ns)/1e9>=60*5):
                log.append('300秒没有按下')
                deepsleep()
                powerofftime_ns = time.time_ns()
                
        else:
            powerofftime_ns=time.time_ns()
            if(key ==keyvalue.short_press):
                metererror=False
                firstpagetime_ns = time.time_ns()
                nextpage()            
           
            elif(key ==keyvalue.double_press):
                metererror=False
                run_485()
            
            elif(key == keyvalue.triple_press):
                metererror=False
                display485end()
            
            elif(key == keyvalue.long_press):
                poweroff()
           

    #启动485读表任务
    def run_485():
        global blustatus
        global Read485Taskstatus
        global display485
        log.append("run_458 Read485Taskstatus:",Read485Taskstatus)
        if(Read485Taskstatus):
            log.append("读表程序正在运行，不能再次进入抄表任务")
            return
        if debugflag:
            blustatus =True#测试时不考虑蓝牙连接状态，运行环境需要注释掉
        display485 =True
        Read485Taskstatus = True
        if (blustatus ==False):
            pagecontrol.clear()
            r485error = [
                        lineinfo(ConnectBluetoothfirst,pixel(0,18),texttype.cn),
                        lineinfo(tryagainreadmeter,pixel(0,35),texttype.cn)
                         ]
            pagecontrol.append(r485error)              
            pagecontrol.nextshow()
            log.append(ConnectBluetoothfirst+tryagainreadmeter)
            time.sleep(3)
            pagecontrol.clear()
            display485 =False
            Read485Taskstatus = False
            return
        _thread.stack_size(1024*8)
        _thread.start_new_thread(readmetertaskmain,('read meter task',lock))
       
 
    #485读表任务
    def readmetertaskmain(name,lock):
        global Read485Taskstatus
        #readmetertask()
        lock.acquire()  # 获取锁
        try:            
            readmetertask(lock)
        except  Exception as e:
            log.append('红外读取任务发生异常:',e)
            pagecontrol.clear()
            r485error = [
                        lineinfo(r485Error,pixel(0,18),texttype.cn),
                        lineinfo(tryagainreadmeter,pixel(0,35),texttype.cn)
                         ]
            pagecontrol.append(r485error)              
            nextpage()
        finally:
            global firstpagetime_ns
            #任务结束开启自动返回首页计时
            firstpagetime_ns= time.time_ns()
            Read485Taskstatus=False
            lock.release()  # 释放锁
            _thread.exit()
                
    #后台显示更新任务          
    def taskshowPage(name,lock):
        global displayNew
        
        while True:
            lock.acquire()  # 获取锁
            starttime = time.time_ns()
            if(displayNew==True):
                pagecontrol.nextshow()
                displayNew =False
                #print("taskshowPage 显示耗时",(time.time_ns()-starttime)/1e9)
            lock.release()  # 释放锁
            time.sleep(0.01)
    #初始化 adc 做N次采集并进行滤波算法稳定adc采样值
    def initadc(mkalman_filter):
        batteryValue=0
        for x in range(1000):
            #lock.acquire()  # 获取锁
            batteryValue=batteryAdc.read()/4095*1.2*112/12/4.7*3.812
            batteryValuekalman=mkalman_filter.kalman(batteryValue)
            #lock.release()  # 释放锁
            time.sleep(0.01)
        return batteryValuekalman
    #电池采样电压任务
    def taskreadadc(name,lock):
        #adc 采样时间判断间隔3秒做 一次
        mkalman_filter =  kalman_filter(0.001,0.1)
        batteryValue=0
        #初始化 adc 做N次采集并进行滤波算法稳定adc采样值
        batteryValuekalman =initadc(mkalman_filter)
        #开始ADC采样       
        adc_ns=time.time_ns()
        file_ns =time.time_ns()
        while(True):
           
             #电池性能测试
            if(debugflag):
                if((time.time_ns()-file_ns)/1e9>=60*3):
                    with open('batteryValue.csv', 'a') as file:
                        t = time.localtime()
                        file.write('\n'+batterystr+'  {}-{}-{} {}:{}:{}'.format(t[0], t[1], t[2], t[3], t[4], t[5]))
                        file.close()
                        lock.acquire()  # 获取锁
                        log.append("写入电压值")
                        lock.release()  # 释放锁
                    file_ns=time.time_ns()
                  
            if((time.time_ns()-adc_ns)/1e9>=30):
                for x in range(20) :
                    i=0
                    while(True):
                        
                        i=i+1
                        #连续10次采样值大于0.01则初始化adc重新修订
                        if(i>10):
                            batteryValuekalman =initadc(mkalman_filter)
                            
                        batteryValue=batteryAdc.read()/4095*1.2*112/12/4.7*3.812
                        if(abs((batteryValue-batteryValuekalman)/batteryValuekalman)<0.01):                            
                            break
                        else:
                            time.sleep(0.01)
                            log.append(batteryValue,batteryValuekalman,
                                  abs((batteryValue-batteryValuekalman)/batteryValuekalman))
                    time.sleep(0.01)
                batteryValuekalman=mkalman_filter.kalman(batteryValue)               
                batterystr ="{:.2f}".format(batteryValuekalman)
                
                lock.acquire()  # 获取锁
                log.append('Thread ID: {}'.format(id(_thread.get_ident())))
                log.append("battery:"+batterystr)
                
                    
                #print(batteryValue,batteryValuekalman)
                if(batteryValuekalman<3.4):
                    log.append("低电关机")
                    ledchrg.value(not ledchrg.value())
                    poweroff()
                if(batteryValuekalman<3.61):
                    log.append("电压超级低关机")
                    ledchrg.value(not ledchrg.value())
                    play_ding(1)
                elif(batteryValuekalman<3.67):
                    log.append("电压低告警")
                    ledchrg.value(not ledchrg.value())
                #if(Read485Taskstatus!=True):                    
                    #filesize =log.getsize('aEm485.log')
                    #print(filesize)
                firstpage(str(batterystr))
                adc_ns=time.time_ns()
                lock.release()  # 释放锁
            time.sleep(0.01) 
                
                
    #********************              
    #主程序主体          * 
    #********************
    log= Logs('amain',not debugflag)
    log.append('程序启动--'+str(not debugflag))
    lock = _thread.allocate_lock()  # 创建一个锁对象           
    power = Pin(ppower, Pin.OUT) #电源开关
    power.value(0)    
    led = Pin(pled, Pin.OUT) # 创建蓝色LED对象
    ledalarm = Pin(palarm,Pin.OUT) 
    lednormal =Pin(pnormal,Pin.OUT)
    #按键灯
    ledkeygreen = Pin(pledkeygreen,Pin.OUT)
    ledkeyred = Pin(pledkeyred,Pin.OUT)
    ledkeyred.value(0)
    ledkeygreen.value(0)
    
    
    ledchrg= Pin(pchrg,Pin.OUT)
    ledstdby = Pin(pstdby,Pin.OUT)
    ledstdby.value(1)
    ledchrg.value(1)
    play_ding(2)
    
    batteryAdc = ADC(Pin(pbatteryadc)) #引脚7跟电池电压测量
    batteryAdc.atten(ADC.ATTN_0DB) #设置衰减比，测量1.2v
    batteryAdc.width(ADC.WIDTH_12BIT)  #设置12位数据宽度
    #初始化液晶屏显示模块
    _display =display()                                                                                     
    #自动换页模块
    pagecontrol =PageControl(_display)

    #蓝牙接收数据
    BleStateTimer = Timer(0)  # 定时器 用于监测蓝牙连接状态 连接后led常亮，断开后 led闪烁，ESP32有4个硬件定时器，此处使用的是0    
    #设置蓝牙连接状态回调函数
    connected_event(False)
    #启动蓝牙设备
    bleop = BLEOP(blename,write_callback=on_rx,condection_event=connected_event)    
    #初始化485读表模块
    em485=Em485()
    #data= em485.fromhex("6832768503170268d101352016")
    #em485.to_data(data)
    #return

    #显示任务
    
    _thread.stack_size(1024*16)
    _thread.start_new_thread(taskshowPage,('showPage task',lock))
    #adc 采样任务
    _thread.stack_size(1024)
    _thread.start_new_thread(taskreadadc,('readadc task',lock))
    
    switch = Switch(Pin(pkey, Pin.IN, Pin.PULL_UP),0,3000,500)  # 时间参数均采用默认值，            
    # 按键未按下、单击、双击、三击、长按分别输出0~5
    switch.no_press_func = lambda: switchpress(keyvalue.no_press)
    switch.short_press_func = lambda:switchpress(keyvalue.short_press)
    switch.double_press_func = lambda: switchpress(keyvalue.double_press)
    switch.triple_press_func = lambda: switchpress(keyvalue.triple_press)
    switch.long_press_func = lambda: switchpress(keyvalue.long_press)
   
    global powerofftime_ns 
    powerofftime_ns= time.time_ns()
    
    # 主循环
    # 配置看门狗，超时时间为1000毫秒（*XX秒）  
    wdt = machine.WDT(timeout=1000*60)
    wdt.feed()
    alarm_ns= time.time_ns()
    global blustatus 
    while True:        
        power.value(0)
        switch.scan()
        lock.acquire()  # 获取锁
        if((time.time_ns()-alarm_ns)/1e9>=3):
            if(blustatus):
                led.value(1)
            if (metererror)  :
                play_di()
                ledalarm.value(not ledalarm())
                lednormal.value(1)
            else:
                if display485:
                      ledalarm.value(1)
                      lednormal.value(0)
                else:
                    lednormal.value(not lednormal())
                    ledalarm.value(1)
            alarm_ns= time.time_ns()
        lock.release()  # 释放锁
        time.sleep(0.01)
        wdt.feed()
                         
if __name__ == "__main__":
    main()